#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#ifdef USE_CAPSICUM
#include <sys/capsicum.h>
#endif

#include <err.h>
#include <sysexits.h>

#include "au.h"
#include "common.h"

#ifndef BSIZE
#define BSIZE 1024
#endif

enum {
	MODE_AU,
	MODE_RAW,
	MODE_NOHDR
};

#ifdef SIGINFO
static size_t	info_in = 0;
static size_t	info_out = 0;
#endif

#ifdef SIGINFO
static void	siginfo(int);
#endif
static size_t	conv(uint32_t, const void *, size_t, uint32_t, void *,
    size_t, size_t);
static size_t	pcmconv(void *, size_t, void *, uint32_t);
static size_t	ulawconv(uint8_t, void *, uint32_t);
static size_t	alawconv(uint8_t, void *, uint32_t);
static size_t	floatconv(void *, size_t, void *, uint32_t);
static ssize_t	ncpy(int, int, void *, size_t);
static unsigned	clz_16(uint16_t);

int
main(int argc, char *argv[])
{
	char hb[AU_SIZE];
#ifdef USE_CAPSICUM
	cap_rights_t rights;
#endif
	struct au iau = AU_INITIALIZER;
	struct au oau = AU_INITIALIZER;
	int (*iswap)(uint32_t, void *, size_t) = NULL;
	int (*oswap)(uint32_t, void *, size_t) = NULL;
	size_t bsize = BSIZE;
	size_t ibsize = 0;
	size_t obsize = 0;
	void *ib = NULL;
	void *ob = NULL;
	const char *cp;
	char *p;
	ssize_t r;
	ssize_t t;
	ssize_t i;
	int imode = MODE_AU;
	int omode = MODE_AU;
	int c;

#ifdef USE_PLEDGE
	pledge("stdio", NULL);
#endif
#ifdef USE_CAPSICUM
	if (cap_enter() == -1 && errno != ENOSYS)
		err(EX_OSERR, "Could not enter capibility mode");
	cap_rights_init(&rights, CAP_READ);
	if (cap_rights_limit(STDIN_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the input");
	cap_rights_init(&rights, CAP_WRITE);
	if (cap_rights_limit(STDOUT_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the output");
	if (cap_rights_limit(STDERR_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard error");
#endif
	while ((c = getopt(argc, argv, "b:c:i:j:o:r:s:")) != -1)
		switch (c) {
		case 'b':
			bsize = strtoul(optarg, &p, 0);
			if (p == optarg || *p != '\0')
				err(EX_USAGE, "Bad number %s", optarg);
			break;
		case 'c':
			iau.au_chan = strtou32(optarg);
			if (imode != MODE_RAW)
				imode = MODE_NOHDR;
			break;
		case 'i':
			imode = MODE_AU;
			if (strncasecmp(optarg, "r", 1) == 0) {
				optarg++;
				if (strncasecmp(optarg, "aw", 2) == 0)
					optarg += 2;
				imode = MODE_RAW;
				if (*optarg == '\0')
					break;
			}
			if ((iau.au_enc = au_strencp(optarg, &cp)) == 0)
				errx(EX_USAGE, "Bad encoding %s",
				    optarg);
			if (imode != MODE_RAW)
				imode = MODE_NOHDR;
			if (*cp == '\0')
				break;
			if (toupper(cp[0]) == 'E' && cp[1] != '\0')
				p++;
			switch (toupper(*cp)) {
			case 'B':
				cp++;
				iswap = NULL;
				imode = MODE_RAW;
				break;
			case 'L':
				cp++;
				iswap = &au_encbswap;
				imode = MODE_RAW;
				break;
			case 'N':
				cp++;
				iswap = &au_encnswap;
				imode = MODE_RAW;
				break;
			default:
				err(EX_USAGE, "Bad encoding %s",
				    optarg);
				break;
			}
			if (toupper(cp[0]) == 'E')
				cp++;
			if (*cp != '\0')
				errx(EX_USAGE, "Bad encoding %s",
				    optarg);
			break;
		case 'j':
			iau.au_off = strtou32(optarg);
			if (imode != MODE_RAW)
				imode = MODE_NOHDR;
			break;
		case 'o':
			omode = MODE_AU;
			if (strncasecmp(optarg, "r", 1) == 0) {
				optarg++;
				if (strncasecmp(optarg, "aw", 2) == 0)
					optarg += 2;
				omode = MODE_RAW;
				if (*optarg == '\0')
					break;
			}
			if ((oau.au_enc = au_strencp(optarg, &cp)) == 0)
				err(EX_USAGE, "Bad encoding %s",
				    optarg);
			if (*cp == '\0')
				break;
			if (toupper(cp[0]) == 'E' && cp[1] != '\0')
				p++;
			switch (toupper(*cp++)) {
			case 'B':
				oswap = NULL;
				imode = MODE_RAW;
				break;
			case 'L':
				oswap = &au_encbswap;
				omode = MODE_RAW;
				break;
			case 'N':
				oswap = &au_encnswap;
				omode = MODE_RAW;
				break;
			default:
				err(EX_USAGE, "Bad encoding %s",
				    optarg);
				break;
			}
			if (toupper(cp[0]) == 'E')
				cp++;
			if (*cp != '\0')
				errx(EX_USAGE, "Bad encoding %s",
				    optarg);
			break;
		case 'r':
			iau.au_rate = strtoul(optarg, &p, 0);
			if (p == optarg || *p != '\0')
				err(EX_USAGE, "Bad number %s", optarg);
			if (imode != MODE_RAW)
				imode = MODE_NOHDR;
			break;
		case 's':
			iau.au_size = strtou32(optarg);
			if (imode != MODE_RAW)
				imode = MODE_NOHDR;
			break;
		default:
			goto usage;
		}
#ifdef SIGINFO
	(void)signal(SIGINFO, &siginfo);
#endif
	while (imode != MODE_AU ||
	    nread(STDIN_FILENO, hb, sizeof(hb)) == sizeof(hb)) {
		if (imode == MODE_AU) {
#ifdef SIGINFO
			info_in += AU_SIZE;
#endif
			if (au_gethdr(&iau, hb) != 0)
				errx(EX_DATAERR, "Bad header");
		}
		if (au_vsize(&iau) == 0)
			err(EX_DATAERR, "Invalid encoding %"PRIu32,
			    iau.au_enc);
		if (ibsize < bsize * au_ssize(&iau)) {
			ibsize = bsize * au_ssize(&iau);
			if ((ib = ib == NULL ? malloc(ibsize) :
			    realloc(ib, ibsize)) == NULL)
				err(EX_OSERR, "Could not allocate "
				    "the input array");
		}
		oau.au_off = iau.au_off;
		oau.au_size = iau.au_size == UINT32_MAX ? UINT32_MAX :
		    iau.au_size / au_vsize(&iau) * au_vsize(&oau);
		oau.au_rate = iau.au_rate;
		oau.au_chan = iau.au_chan;
		if (obsize < bsize * au_ssize(&oau)) {
			obsize = bsize * au_ssize(&oau);
			if ((ob = ob == NULL ? malloc(obsize) :
			    realloc(ob, obsize)) == NULL)
				err(EX_OSERR, "Could not allocate "
				    "the output array");
		}
		if (omode == MODE_AU) {
			(void)au_puthdr(&oau, hb);
			if (nwrite(STDOUT_FILENO, hb, sizeof(hb)) !=
			    sizeof(hb))
				err(EX_IOERR,
				    "Could not write audio header");
#ifdef SIGINFO
			info_out += AU_SIZE;
#endif
		}
		if (omode == MODE_RAW) {
			for (i = AU_SIZE; i < (size_t)iau.au_off;
			    i += r) {
				if ((r = nread(STDIN_FILENO, ib,
				    MIN(iau.au_off - i, bsize))) < 0)
					err(EX_IOERR,
					    "error while reading");
#ifdef SIGINFO
				info_in += r;
#endif
			}
		} else {
			/* ncpy handles info_in/info_out */
			for (i = AU_SIZE; i < (size_t)iau.au_off;
			    i += r)
				if ((r = ncpy(STDIN_FILENO,
				    STDOUT_FILENO, ib, MIN(
				    iau.au_off - i, bsize))) < 0)
					err(EX_IOERR,
					    "error while copying "
					    "stdin -> stdout");
		}
		while (iau.au_size > 0 && (r = nread(STDIN_FILENO, ib,
		    MIN(iau.au_size, ibsize))) > 0) {
#ifdef SIGINFO
			info_in += r;
#endif
			if (iswap != NULL)
				iswap(iau.au_enc, ib, r);
			if ((t = conv(iau.au_enc, ib, r, oau.au_enc, ob,
			    obsize, 0)) < 0)
				abort();
			if (oswap != NULL)
				oswap(oau.au_enc, ob, t);
			if (nwrite(STDOUT_FILENO, ob, t) != t) {
				if (iau.au_size != UINT32_MAX)
					err(EX_IOERR,
					    "Write error");
				goto ret;
			}
#ifdef SIGINFO
			info_out += t;
#endif
			if (iau.au_size != UINT32_MAX)
				iau.au_size -= r;
		}
		if (r < 0)
			err(EX_IOERR, "Error reading from the "
			    "standard input");
		if (imode == MODE_RAW)
			break;
		if (imode == MODE_NOHDR)
			imode = MODE_AU;
	}
ret:
	if (ib != NULL)
		free(ib);
	if (ob != NULL)
		free(ob);
	return (EX_OK);
usage:
	(void)fprintf(stderr,
	    "usage: auconv "
	    "[-b buffer-size] [-c channels] [-i input-encoding]\n"
	    "	      "
	    "[-j skip] [-o output-encoding] [-r rate] [-s size]\n");
	return (EX_USAGE);
}

#ifdef SIGINFO
void
siginfo(int sig)
{
	/* auconv: XXXXXXiB in XXXXXXiB out */
	char b[8 + 8 + 4 + 8 + 5];
	size_t i = 0;

	(void)memcpy(b + i, "auconv: ", 8);
	i += 8;
	(void)memcpy(b + i, ": ", 2);
	i += 2;
	i += szwrite(b + i, info_in, "B", "iB");
	(void)memcpy(b + i, " in ", 4);
	i += 4;
	i += szwrite(b + i, info_out, "B", "iB");
	(void)memcpy(b + i, " out\n ", 5);
	i += 5;
	(void)write(STDERR_FILENO, b, i);
}
#endif

size_t
conv(uint32_t ienc, const void *ib, size_t isize, uint32_t oenc,
    void *ob, size_t osize, size_t skip)
{
	size_t i;
	size_t j = 0;
	size_t o;
	size_t off;

	assert(ib != NULL && ob != NULL);
	if (osize == 0)
		return (0);
	o = au_encsize(ienc);
	off = o + o * skip;
	if (ienc == oenc) {
		if (off == o) {
			i = isize > osize ? osize : isize;
			(void)memmove(ob, ib, i);
			return (i);
		}
		for (i = 0, j = 0; i < isize && j < osize;
		    i += off, j += o)
			(void)memmove((void *)((uintptr_t)ob + j),
			    (void *)((uintptr_t)ib + i), o);
	}
	switch (ienc) {
	case AU_ULAW:
		for (i = 0, j = 0; i < isize && j < osize; i += off)
			j += ulawconv(*(uint8_t *)((uintptr_t)ib + i),
			    (void *)((uintptr_t)ob + j), oenc);
		break;
	case AU_PCM8:
		/* FALLTHROUGH */
	case AU_PCM16:
		/* FALLTHROUGH */
	case AU_PCM24:
		/* FALLTHROUGH */
	case AU_PCM32:
		for (i = 0, j = 0; i < isize && j < osize; i += off)
			j += pcmconv((void *)((uintptr_t)ib + i), o,
			    (void *)((uintptr_t)ob + j), oenc);
		break;
	case AU_FLOAT32:
		/* FALLTHROUGH */
	case AU_FLOAT64:
		for (i = 0, j = 0; i < isize && j < osize; i += off)
			j += floatconv((void *)((uintptr_t)ib + i), o,
			    (void *)((uintptr_t)ob + j), oenc);
		break;
	case AU_ALAW:
		for (i = 0, j = 0; i < isize && j < osize; i += off)
			j += alawconv(*(uint8_t *)((uintptr_t)ib + i),
			    (void *)((uintptr_t)ob + j), oenc);
		break;
	}
	return (j);
}

size_t
pcmconv(void *ib, size_t io, void *ob, uint32_t oenc)
{
	const uint32_t end = 1;
	char b[sizeof(double)];
	size_t i;
	size_t t;
	uint32_t pcm;

	switch (oenc) {
	case AU_ULAW:
		if (io == 1) { /* expand to 14-bits */
			pcm = *(uint8_t *)ib;
			pcm = (pcm << 6) | (pcm >> 2);
		} else /* shrink to 14-bits */
			pcm = (((uint8_t *)ib)[0] << 6) |
			    (((uint8_t *)ib)[1] >> 2);
		if (pcm & 0x2000) {
			pcm = ~pcm & 0x1FFF;
			i = 0;
		} else
			i = 0x80;
		if (pcm < 0x2000 - 33)
			pcm += 33;
		t = clz_16(pcm) - 3;
		*(uint8_t *)ob = i | (t << 4) |
		    (~(pcm >> (8 - t)) & 0x0F);
		return (1);
	case AU_PCM8:
		/* FALLTHROUGH */
	case AU_PCM16:
		/* FALLTHROUGH */
	case AU_PCM24:
		/* FALLTHROUGH */
	case AU_PCM32:
		for (i = 0, t = au_encsize(oenc); i < t; i++)
			((uint8_t *)ob)[i] = ((uint8_t *)ib)[i % io];
		return (t);
	case AU_FLOAT32:
		/* FALLTHROUGH */
	case AU_FLOAT64:
		for (i = 0, pcm = 0; i < io; i++)
			pcm = pcm << 8 | ((uint8_t *)ib)[i];
		t = UINT32_C(1) << ((io * 8) - 1);
		if (pcm & t) {
			pcm = (~pcm & (t - 1)) + 1;
			i = 0;
		} else
			i = 1;
		if (oenc == AU_FLOAT32) {
			*(float *)b = (float)pcm / (float)(t - i);
			if (!i)
				*(float *)b = -*(float *)b;
			t = sizeof(float);
		} else {
			*(double *)b = (double)pcm / (double)(t - i);
			if (!i)
				*(double *)b = -*(double *)b;
			t = sizeof(double);
		}
		if (*(char *)&end)
			for (i = 0; i < t; i++)
				((char *)ob)[i] = b[t - i - 1];
		else
			for (i = 0; i < t; i++)
				((char *)ob)[i] = b[i];
		return (t);
	case AU_ALAW:
		if (io == 1) { /* expand to 13-bits */
			pcm = *(uint8_t *)ib;
			pcm = (pcm << 5) | (pcm >> 3);
		} else /* shrink to 13-bits */
			pcm = (((uint8_t *)ib)[0] << 5) |
			    (((uint8_t *)ib)[1] >> 3);
		if (pcm & 0x1000) {
			pcm = ~pcm & 0x0FFF;
			i = 0;
		} else
			i = 0x80;
		if ((t = clz_16(pcm) - 4) >= 7)
			*(uint8_t *)ob = i | ((pcm >> 1) & 0x0F);
		else
			*(uint8_t *)ob = i | ((7 - t) << 4) |
			    ((pcm >> (7 - t)) & 0x0F);
		*(uint8_t *)ob ^= 0x55;
		return (1);
	}
	abort();
}

size_t
ulawconv(uint8_t u, void *ob, uint32_t oenc)
{
	unsigned char b[sizeof(uint16_t)];
	uint16_t pcm;
	uint16_t p;

	if (oenc == AU_ULAW) {
		*(uint8_t *)ob = u;
		return (1);
	}
	u = ~u;
	p = (u >> 4) & 7;
	pcm = ((u & 0x0F) << (p + 1)) | (0x21 << p);
	pcm = ((pcm << 2) | (pcm >> 12)) - 33;
	if (u & 0x80)
		pcm = ~pcm;
	for (p = 0; p < sizeof(b); p++)
		b[p] = pcm >> (sizeof(b) - p - 1) * CHAR_BIT;
	return (pcmconv(b, sizeof(uint16_t), ob, oenc));
}

size_t
alawconv(uint8_t a, void *ob, uint32_t oenc)
{
	unsigned char b[sizeof(uint16_t)];
	uint16_t pcm;
	uint16_t p;

	if (oenc == AU_ALAW) {
		*(uint8_t *)ob = a;
		return (1);
	}
	a ^= 0xD5;
	if ((p = (a >> 4) & 7) == 0)
		pcm = ((a & 0x0F) << 1) | 1;
	else
		pcm = ((a & 0x0F) << p) | (0x21 << (p - 1));
	pcm = (pcm << 3) | (pcm >> 10);
	if (a & 0x80)
		pcm = ~pcm;
	for (p = 0; p < sizeof(b); p++)
		b[p] = pcm >> (sizeof(b) - p - 1) * CHAR_BIT;
	return (pcmconv(b, sizeof(uint16_t), ob, oenc));
}

size_t
floatconv(void *ib, size_t io, void *ob, uint32_t oenc)
{
	const uint32_t end = 1;
	unsigned char b[sizeof(double)];
	uint32_t pcm;
	uint32_t t = 0;
	double d;
	size_t i;

	/* and begin incantations */
	switch (oenc) {
	case AU_ULAW:
		(void)floatconv(ib, io, b, AU_PCM16);
		return (pcmconv(b, sizeof(uint16_t), ob, oenc));
		break;
	case AU_PCM8:
		/* FALLTHROUGH */
	case AU_PCM16:
		/* FALLTHROUGH */
	case AU_PCM24:
		/* FALLTHROUGH */
	case AU_PCM32:
		t = oenc - AU_PCM8 + 1;
		if (*(char *)&end)
			for (i = 0; i < io; i++)
				b[i] = ((char *)ib)[io - i - 1];
		else
			for (i = 0; i < io; i++)
				b[i] = ((char *)ib)[i];
		if (io == sizeof(float))
			d = *(float *)b;
		else
			d = *(double *)b;
		if (d < 0) {
			d = -d;
			i = 0;
		} else
			i = 1;
		d *= (UINT32_C(1) << (t * 8 - 1)) - i;
		pcm = (uint32_t)d;
		if (!i)
			pcm = ~pcm + 1;
		for (i = 0; i < t; i++)
			((uint8_t *)ob)[i] = pcm >> (t - i - 1) * 8;
		return (t);
	case AU_FLOAT32:
		if (io == sizeof(float)) {
			(void)memcpy(ob, ib, io);
			return (io);
		}
		/*
		 * 			Expanded Float	Double
		 * 			sabcdefg	sabcdefg
		 * mantissa loop ->	habc*123	hijk0123
		 * 			45670123	45670123
		 * 			45670123	45670123
		 * mantissa loop ->	456*1234	45670123
		 * 		 	56701234	45670123
		 * 			56701234	45670123
		 * mantissa loop ->	56*12345	45670123
		 */
		((uint8_t *)ob)[0] = ((uint8_t *)ib)[0];
		((uint8_t *)ob)[1] = (((uint8_t *)ib)[1] & 0x80) |
		    (((uint8_t *)ob)[0] & 0x70) |
		    ((((uint8_t *)ib)[1] >> 3) & 0x0F);
		((uint8_t *)ob)[2] = (((uint8_t *)ib)[1] << 5) |
		    ((((uint8_t *)ib)[2] >> 3) & 0x1F);
		((uint8_t *)ob)[3] = (((uint8_t *)ib)[2] << 5) |
		    ((((uint8_t *)ib)[3] >> 3) & 0x1F);
		((uint8_t *)ob)[4] = (((uint8_t *)ib)[3] << 5) |
		    ((((uint8_t *)ib)[1] >> 2) & 0x1F);
		((uint8_t *)ob)[5] = (((uint8_t *)ib)[1] << 6) |
		    ((((uint8_t *)ib)[2] >> 2) & 0x1F);
		((uint8_t *)ob)[6] = (((uint8_t *)ib)[2] << 6) |
		    ((((uint8_t *)ib)[3] >> 2) & 0x1F);
		((uint8_t *)ob)[7] = (((uint8_t *)ib)[3] << 6) |
		    ((((uint8_t *)ib)[1] >> 1) & 0x1F);
		return (sizeof(double));
	case AU_FLOAT64:
		if (io == sizeof(double)) {
			(void)memcpy(ob, ib, io);
			return (io);
		}
		((uint8_t *)ob)[0] = ((uint8_t *)ib)[0];
		((uint8_t *)ob)[1] = (((uint8_t *)ib)[1] & 0x80) |
		    ((((uint8_t *)ib)[1] << 3) & 0x70) |
		    (((uint8_t *)ib)[2] >> 5);
		((uint8_t *)ob)[2] = (((uint8_t *)ib)[2] << 3) |
		    (((uint8_t *)ib)[3] >> 5);
		((uint8_t *)ob)[3] = (((uint8_t *)ib)[3] << 3) |
		    (((uint8_t *)ib)[4] >> 5);
		return (sizeof(float));
	case AU_ALAW:
		(void)floatconv(ib, io, b, AU_PCM16);
		return (pcmconv(b, sizeof(uint16_t), ob, oenc));
		break;
	}
	abort();
}

ssize_t
ncpy(int in, int out, void *b, size_t s)
{
	ssize_t t;

	if ((t = nread(in, b, s)) > 0) {
#ifdef SIGINFO
		info_in += t;
#endif
		t = nwrite(out, b, s);
#ifdef SIGINFO
		info_out += t;
#endif
	}
	return (t);
}

unsigned
clz_16(uint16_t v)
{
	unsigned r = 0;

	if ((v & 0xFF00) == 0) {
		v <<= 8;
		r += 8;
	}
	if ((v & 0xF000) == 0) {
		v <<= 4;
		r += 4;
	}
	if ((v & 0xC000) == 0) {
		v <<= 2;
		r += 2;
	}
	r += (uint16_t)~v >> 15;
	return (r);
}
